﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Common;
using System.Xml;
using System.Data;
using System.IO;
using MacomberMapSystem.Common.CIM;
using System.Drawing;
using MacomberMapSystem.Common.Errors;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.Internals;
using MacomberMapSystem.Common.Integration;

namespace MacomberMapSystem.Common.Serialization
{
    /// <summary>
    /// This class allows components to be serialized in XML.
    /// </summary>
    public abstract class MM_Serializable
    {
        #region Variable declarations
        /// <summary>The unique identifier for this object</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldUnique)]
        public int ID = int.MinValue;
        #endregion

        #region Initialization
        /// <summary>
        /// Instantiate a serializable object based on a data reader
        /// </summary>
        /// <param name="dRd">The data reader with information for the class</param>
        /// <param name="xDoc">The optional XML document in which strings converted to XML are stored</param>
        /// <param name="Repository">The repository that may contain needed components</param>
        public MM_Serializable(DbDataReader dRd, XmlDocument xDoc, MM_Repository Repository)
        {
            if (dRd == null)
                return;
            else
                InitializeFromDatabase(dRd, xDoc, Repository, this);
        }

        /// <summary>
        /// Instantiate a serializable object based on an XML element
        /// </summary>      
        /// <param name="xElem">The XML element containing the configuration parameters for our element</param>
        /// <param name="Repository">The repository that may contian additional needed information</param>
        public MM_Serializable(XmlElement xElem, MM_Repository Repository)
        {
            if (xElem != null)
                InitializeFromXml(this, xElem, Repository);
        }

        /// <summary>
        /// Initialize an object from its XML definition
        /// </summary>
        /// <param name="TargetObject"></param>
        /// <param name="Repository">The repository that may contian additional needed information</param>
        /// <param name="xElem"></param>
        public static void InitializeFromXml(Object TargetObject, XmlElement xElem, MM_Repository Repository)
        {
            XmlAttribute[] AttrToProcess = new XmlAttribute[xElem.Attributes.Count];
            xElem.Attributes.CopyTo(AttrToProcess, 0);
            foreach (XmlAttribute xAttr in AttrToProcess)
                try
                {
                    AssignValue(TargetObject, xAttr.Name, xAttr.Value, null, Repository);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error when processing XML attribute {0}={1}: {2}", xAttr.Name, xAttr.Value, ex.Message);
                }
        }

        /// <summary>
        /// Initialize an object from our parameters
        /// </summary>
        /// <param name="dRd"></param>
        /// <param name="xDoc"></param>
        /// <param name="Repository"></param>
        /// <param name="TargetObject"></param>
        public static void InitializeFromDatabase(DbDataReader dRd, XmlDocument xDoc, MM_Repository Repository, Object TargetObject)
        {
            MM_Serializable_Target.enumTargetType Target = MM_Serializable_Target.enumTargetType.None;
            Dictionary<String, MemberInfo> ParamValues = new Dictionary<string, MemberInfo>();
            foreach (MemberInfo mI in TargetObject.GetType().GetMembers(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic))
                if ((mI is FieldInfo || mI is PropertyInfo) && ((Target = MM_Serializable_Target.GetTargetType(mI)) != MM_Serializable_Target.enumTargetType.None))
                    try
                    {
                        if (Target == MM_Serializable_Target.enumTargetType.FieldValue)
                            AssignValue(TargetObject, mI.Name, dRd[mI.Name], xDoc, Repository);
                        else if (Target == MM_Serializable_Target.enumTargetType.FieldUnique)
                            AssignValue(TargetObject, mI.Name, dRd[mI.Name], xDoc, Repository);
                        else if (Target == MM_Serializable_Target.enumTargetType.ParameterXml)
                            AssignValue(TargetObject, mI.Name, dRd["Parameters"], xDoc, Repository);
                        else if (Target == MM_Serializable_Target.enumTargetType.ParameterValue)
                            ParamValues.Add(mI.Name, mI);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.GetType().Name + " when attempting to assign value " + mI.Name + " to " + TargetObject.GetType().Name + ": " + ex.Message);
                    }
            String InLine;
            if (ParamValues.Count > 0)
                using (StringReader sRd = new StringReader((string)dRd["Parameters"]))
                    while (!String.IsNullOrEmpty(InLine = sRd.ReadLine()))
                        AssignValue(TargetObject, InLine.Substring(0, InLine.IndexOf('=')), InLine.Substring(InLine.IndexOf('=') + 1), xDoc, Repository);
        }

        /// <summary>
        /// Initialize an object from a set of parameters
        /// </summary>
        /// <param name="TargetObject"></param>
        /// <param name="Parameters"></param>
        /// <param name="Repository">The repository that may contian additional needed information</param>        
        public static void InitializeFromParameters(Object TargetObject, PropertyCollection Parameters, MM_Repository Repository)
        {
            try
            {
                System.Collections.IDictionaryEnumerator kvp = Parameters.GetEnumerator();
                while (kvp.MoveNext())                
                    try
                    {
                        AssignValue(TargetObject, (string)kvp.Key, kvp.Value, null, Repository);
                    }
                    catch (Exception ex)
                    {
                        MM_Event.LogError("Error de-serializing property " + kvp.Key + ": " + ex.Message, ex);
                    }
            }
            catch (Exception ex)
            {
                MM_Event.LogError("Error de-serializing property " + ex.Message, ex);
            }
        }
        #endregion

        #region Value reading and conversions
        /// <summary>
        /// Assign a value to an element
        /// </summary>
        /// <param name="TargetObject"></param>
        /// <param name="MemberName"></param>
        /// <param name="inValue"></param>
        /// <param name="xDoc"></param>
        /// <param name="Repository">The repository that may contian additional needed information</param>
        public static void AssignValue(Object TargetObject, String MemberName, Object inValue, XmlDocument xDoc, MM_Repository Repository)
        {
            String[] splStr = MemberName.Split('.');
            for (int a = 0; a < splStr.Length; a++)
            {
                MemberInfo[] mI = TargetObject.GetType().GetMember(splStr[a], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase);
                if (mI.Length == 0)
                    return;
                else if (a == splStr.Length-1 && mI[0] is FieldInfo)
                    (mI[0] as FieldInfo).SetValue(TargetObject, ConvertValue((mI[0] as FieldInfo).FieldType, inValue, xDoc, Repository));                    
                else if (a == splStr.Length-1 && mI[0] is PropertyInfo && (mI[0] as PropertyInfo).CanWrite)
                    (mI[0] as PropertyInfo).SetValue(TargetObject, ConvertValue((mI[0] as PropertyInfo).PropertyType, inValue, xDoc, Repository), null);
                else if (mI[0] is FieldInfo)
                    TargetObject = (mI[0] as FieldInfo).GetValue(TargetObject);
                else if (mI[0] is PropertyInfo)                    
                    TargetObject = (mI[0] as PropertyInfo).GetValue(TargetObject, null);
            }

            foreach (MemberInfo mI in TargetObject.GetType().GetMember(MemberName))
                if (mI is FieldInfo)
                    (mI as FieldInfo).SetValue(TargetObject, ConvertValue((mI as FieldInfo).FieldType, inValue, xDoc, Repository));
            else if (mI is PropertyInfo)
                    (mI as PropertyInfo).SetValue(TargetObject, ConvertValue((mI as PropertyInfo).PropertyType, inValue, xDoc, Repository), null);
                       
        }

        /// <summary>
        /// Convert an incoming value to a target type
        /// </summary>
        /// <param name="TargetType"></param>
        /// <param name="TargetValue"></param>
        /// <param name="xDoc"></param>
        /// <param name="Repository">The repository that may contian additional needed information</param>        
        /// <returns></returns>
        public static Object ConvertValue(Type TargetType, Object TargetValue, XmlDocument xDoc, MM_Repository Repository)
        {
            FieldInfo fI;
            if (TargetValue == null || TargetValue is DBNull)
                return null;
            else if (TargetType == TargetValue.GetType())
                return TargetValue;
            else if (TargetType == typeof(Image) && TargetValue is byte[])
                using (MemoryStream inStream = new MemoryStream((byte[])TargetValue))
                    return Image.FromStream(inStream);
            else if (TargetType == typeof(Image) & TargetValue is String)
                using (MemoryStream inStream = new MemoryStream(Convert.FromBase64String((string)TargetValue)))
                    return Image.FromStream(inStream);
            else if (TargetType == typeof(Bitmap) && TargetValue is byte[])
                using (MemoryStream inStream = new MemoryStream((byte[])TargetValue))
                    return Bitmap.FromStream(inStream);
            else if (TargetType == typeof(Bitmap) & TargetValue is String)
                using (MemoryStream inStream = new MemoryStream(Convert.FromBase64String((string)TargetValue)))
                    return Bitmap.FromStream(inStream);
            else if (TargetType.IsEnum && TargetValue is string)
                return Enum.Parse(TargetType, (string)TargetValue);
            else if (TargetType.IsEnum)
                return Enum.GetValues(TargetType).GetValue(Convert.ToInt32(TargetValue));
            else if (TargetType.IsValueType && TargetValue.GetType().IsValueType)
                return Convert.ChangeType(TargetValue, TargetType);
            else if (TargetType == typeof(XmlDocument) && TargetValue is string)
            {

                XmlDocument OutDoc = new XmlDocument();
                if ((TargetValue as string)[0] == 191)
                    OutDoc.LoadXml((TargetValue as string).Substring(1));
                else
                    OutDoc.LoadXml(TargetValue as string);
                return OutDoc;
            }
            else if (TargetType == typeof(CIM_RdfID) && TargetValue is string)
                return new CIM_RdfID((string)TargetValue);
            else if (TargetType == typeof(CIM_RdfID) && TargetValue is byte[])
                return new CIM_RdfID((byte[])TargetValue);
            else if (Repository != null && (fI = Repository.GetType().GetField(TargetType.Name.Substring(TargetType.Name.LastIndexOf("_") + 1) + "_Collection")) != null)
            {
                Object InVal = fI.GetValue(Repository);
                if (InVal is System.Collections.IDictionary)
                    return (InVal as System.Collections.IDictionary)[Convert.ToInt32(TargetValue)];
                else
                    return null;
            }
            else if (TargetType.IsArray && TargetValue is string)
            {
                String[] splStr = (TargetValue as string).Split(',');
                Array OutResult = Array.CreateInstance(TargetType.GetElementType(), splStr.Length);
                for (int a = 0; a < splStr.Length; a++)
                    if (!String.IsNullOrEmpty(splStr[a]))
                        OutResult.SetValue(ConvertValue(TargetType.GetElementType(), splStr[a], xDoc, Repository), a);
                return OutResult;
            }
            else if (TargetType == typeof(MM_String_Collection))
            {
                MM_String_Collection OutCollection = new MM_String_Collection();
                foreach (string str in (TargetValue as string).Split(','))
                    if (!String.IsNullOrEmpty(str))
                        OutCollection.Add(str);
                return OutCollection;
            }
            else if (TargetType == typeof(CIM_Link[]) && TargetValue is byte[])
            {
                byte[] InBytes = (byte[])TargetValue;
                CIM_Link[] OutLinks = new CIM_Link[InBytes.Length / 5];
                for (int a = 0; a < InBytes.Length; a += 5)
                    OutLinks[a / 5] = new CIM_Link(InBytes, a, Repository);
                return OutLinks;
            }
            else if (TargetType == typeof(Rectangle) && TargetValue is string)
            {
                string[] splStr = (TargetValue as string).Split(',');
                return new Rectangle(int.Parse(splStr[0]), int.Parse(splStr[1]), int.Parse(splStr[2]), int.Parse(splStr[3]));
            }
            else if (TargetType == typeof(PointF) && TargetValue is string)
            {
                string[] splStr = (TargetValue as string).Split(',');
                return new PointF(float.Parse(splStr[0]), float.Parse(splStr[1]));
            }
            else if (TargetType == typeof(PointD) && TargetValue is string)
            {
                string[] splStr = (TargetValue as string).Split(',');
                return new PointD(double.Parse(splStr[0]), double.Parse(splStr[1]));
            }
            else if (TargetType == typeof(Color) && TargetValue is string)
                return ColorTranslator.FromHtml((string)TargetValue);
            else if (TargetType == typeof(Font) && TargetValue is string)
                return new FontConverter().ConvertFromString((string)TargetValue);
            else if (TargetType == typeof(PointD[]) && TargetValue is byte[])
            {
                byte[] inBytes = (byte[])TargetValue;
                List<PointD> OutPoints = new List<PointD>(inBytes.Length / 16);
                for (int a = 0; a < inBytes.Length; a += 16)
                    OutPoints.Add(new PointD(BitConverter.ToDouble(inBytes, a), BitConverter.ToDouble(inBytes, a + 8)));
                return OutPoints.ToArray();
            }
            else if (TargetType == typeof(MM_Boundary[]) && TargetValue is byte[])
            {
                byte[] inBytes = (byte[])TargetValue;
                List<MM_Boundary> OutPoints = new List<MM_Boundary>(inBytes.Length / 4);
                for (int a = 0; a < inBytes.Length; a += 16)
                    OutPoints.Add(Repository.Db.Boundaries[BitConverter.ToInt32(inBytes, a)]);
                return OutPoints.ToArray();
            }
            else if (TargetType == typeof(MM_Boundary))
                return Repository.Db.Boundaries[Convert.ToInt32(TargetValue)];
            else if (TargetType == typeof(MM_Substation))
                return Repository.Db.Substations[Convert.ToInt32(TargetValue)];
            else if (TargetType == typeof(MM_Line))
                return Repository.Db.Lines[Convert.ToInt32(TargetValue)];
            else if (TargetType == typeof(MM_KVLevel))
            {
                MM_KVLevel FoundKV;                
                if (TargetValue is string && Data_Manager.KVLevels.TryGetValue(TargetValue.ToString(), out FoundKV))
                    return FoundKV;
                else if (Data_Manager.KVLevels.TryGetValue(Convert.ToInt32(TargetValue), out FoundKV))
                    return FoundKV;
                else
                    return Data_Manager.KVLevels[Convert.ToInt32(TargetValue)];
            }                           
            else if (TargetType == typeof(MM_KVLevel[]))
            {
                int CurVal = Convert.ToInt32(TargetValue);
                List<MM_KVLevel> KVLevels = new List<MM_KVLevel>();
                foreach (MM_KVLevel KVLevel in Data_Manager.KVLevels.Values)
                    if ((CurVal & KVLevel.ID) == KVLevel.ID)
                        KVLevels.Add(KVLevel);
                return KVLevels.ToArray();
            }
            else if (TargetType.IsValueType)
                return Convert.ChangeType(TargetValue, TargetType);
            else
                return null;
        }

        /// <summary>
        /// Write or update an attribute value
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="AttributeName"></param>
        /// <param name="AttributeValue"></param>
        public static void WriteAttribute(XmlElement xElem, String AttributeName, Object AttributeValue)
        {
            XmlAttribute xAttr = xElem.Attributes[AttributeName];
            if (xAttr == null)
                xAttr = xElem.Attributes.Append(xElem.OwnerDocument.CreateAttribute(AttributeName));
            xAttr.Value = GetStringForXml(AttributeValue);                       
        }

        /// <summary>
        /// Get the string for Xml
        /// </summary>
        /// <param name="AttributeValue"></param>
        /// <returns></returns>
        public static String GetStringForXml(Object AttributeValue)
        {
            if (AttributeValue.GetType().IsArray)
            {
                StringBuilder sB = new StringBuilder();
                foreach (Object obj in (AttributeValue as Array))
                    sB.Append((sB.Length == 0 ? "" : ",") + GetStringForXml(obj));
                return sB.ToString();
            }
            else if (AttributeValue is DateTime)
                return XmlConvert.ToString((DateTime)AttributeValue, XmlDateTimeSerializationMode.Unspecified);
            else if (AttributeValue is Boolean)
                return XmlConvert.ToString((Boolean)AttributeValue);
            else if (AttributeValue is Rectangle)
            {
                Rectangle xRect = (Rectangle)AttributeValue;
                return String.Format("{0},{1},{2},{3}", xRect.Left, xRect.Top, xRect.Width, xRect.Height);
            }
            else if (AttributeValue is RectangleF)
            {
                RectangleF xRect = (RectangleF)AttributeValue;
                return String.Format("{0},{1},{2},{3}", xRect.Left, xRect.Top, xRect.Width, xRect.Height);
            }
            else if (AttributeValue is Point)
            {
                Point xPt = (Point)AttributeValue;
                return String.Format("{0},{1}", xPt.X, xPt.Y);
            }
            else if (AttributeValue is PointF)
            {
                PointF xPt = (PointF)AttributeValue;
                return String.Format("{0},{1}", xPt.X, xPt.Y);
            }
            else if (AttributeValue is PointD)
            {
                PointD xPt = (PointD)AttributeValue;
                return String.Format("{0},{1}", xPt.X, xPt.Y);
            }
            else if (AttributeValue.GetType().IsEnum)
                return AttributeValue.ToString();
            else if (AttributeValue is Image)
                using (MemoryStream mS = new MemoryStream())
                {
                    (AttributeValue as Image).Save(mS, System.Drawing.Imaging.ImageFormat.Jpeg);
                    return Convert.ToBase64String(mS.ToArray());
                }
            else if (AttributeValue is Font)
                return new FontConverter().ConvertToString(AttributeValue);
            else if (AttributeValue is Color)
                return ColorTranslator.ToHtml((Color)AttributeValue);
            else
                return AttributeValue.ToString();
        }

        /// <summary>
        /// Determine whether an object can be turned into a number
        /// </summary>
        /// <param name="InValue">The value ot test</param>
        /// <returns></returns>
        private static bool IsNumeric(Object InValue)
        {
            Type ThisType = InValue.GetType();
            if (ThisType == typeof(Int16) || ThisType == typeof(UInt16) ||
                ThisType == typeof(Int32) || ThisType == typeof(UInt32) ||
                ThisType == typeof(Int64) || ThisType == typeof(UInt64) ||
                ThisType == typeof(Single) || ThisType == typeof(Double) ||
                ThisType == typeof(Decimal) || ThisType == typeof(Byte) ||
                ThisType == typeof(SByte))
                return true;
            else if (ThisType == typeof(string))
            {
                Decimal OutAttempt;
                return Decimal.TryParse((string)InValue, out OutAttempt);
            }
            else
                return false;
        }
        #endregion

        #region Value writing
        /// <summary>
        /// Report the current target type
        /// </summary>
        public MM_Serializable_Target TargetType
        {
            get
            {
                foreach (object obj in this.GetType().GetCustomAttributes(true))
                    if (obj is MM_Serializable_Target)
                        return (obj as MM_Serializable_Target);
                return null;
            }
        }

        /// <summary>
        /// Update an existing entry in our RDB
        /// </summary>
        /// <param name="Repository"></param>
        public void UpdateRdb(MM_Repository Repository)
        {
            Dictionary<String, Object> OutValues = new Dictionary<string, Object>();
            Dictionary<String, Object> OutIndexes = new Dictionary<string, object>();
            StringBuilder OutParams = new StringBuilder();
            foreach (MemberInfo mI in this.GetType().GetMembers(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic))
                if (mI is PropertyInfo || mI is FieldInfo)
                {
                    MM_Serializable_Target.enumTargetType Target = MM_Serializable_Target.GetTargetType(mI);
                    if (Target == MM_Serializable_Target.enumTargetType.FieldValue)
                        OutValues.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.FieldUnique)
                        OutIndexes.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterValue)
                        OutParams.AppendLine(mI.Name + "=" + GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterXml)
                        OutParams.AppendLine((string)GetValueForOutput(mI));
                }

            using (DbCommand dCmd = Repository.Db.Database.CreateCommand())
            {
                if (OutParams.Length > 0)
                    OutValues.Add("Parameters", OutParams.ToString());
                List<String> ParamNames = new List<string>(OutValues.Keys);
                ParamNames.AddRange(OutIndexes.Keys);
                
                StringBuilder OutSql = new StringBuilder("UPDATE " + TableName(Repository));
                bool FirstVariable = true;
                foreach (String Param in OutValues.Keys)                   
                    {
                        OutSql.Append((FirstVariable ? " SET " : ", ") +  "\"" + Param.ToUpper() + "\"=:" + Param);
                        FirstVariable = false;
                    }

                

                FirstVariable = true;
                if (OutIndexes.Count > 0)                                    
                    foreach (String Param in OutIndexes.Keys)
                    {
                        OutSql.Append((FirstVariable ? " WHERE " : " AND ") + "\"" + Param.ToUpper() + "\"=:" + Param);
                        FirstVariable =false;
                    }

                dCmd.CommandText = OutSql.ToString();
                dCmd.Prepare();

                foreach (KeyValuePair<String, Object> kvp in OutValues)
                    dCmd.Parameters.Add(Repository.Db.CreateParameter(kvp.Key, kvp.Value));                    
                foreach (KeyValuePair<String, Object> kvp in OutIndexes)
                    dCmd.Parameters.Add(Repository.Db.CreateParameter(kvp.Key, kvp.Value));                    

                using (DbDataReader dRd = dCmd.ExecuteReader())
                { }
                //dCmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Update an existing entry in our RDB
        /// </summary>
        /// <param name="dbConn"></param>
        public void UpdateRdb(DbConnection dbConn)
        {
            Dictionary<String, Object> OutValues = new Dictionary<string, Object>();
            Dictionary<String, Object> OutIndexes = new Dictionary<string, object>();
            StringBuilder OutParams = new StringBuilder();
            foreach (MemberInfo mI in this.GetType().GetMembers(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic))
                if (mI is PropertyInfo || mI is FieldInfo)
                {
                    MM_Serializable_Target.enumTargetType Target = MM_Serializable_Target.GetTargetType(mI);
                    if (Target == MM_Serializable_Target.enumTargetType.FieldValue)
                        OutValues.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.FieldUnique)
                        OutIndexes.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterValue)
                        OutParams.AppendLine(mI.Name + "=" + GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterXml)
                        OutParams.AppendLine((string)GetValueForOutput(mI));
                }

            using (DbCommand dCmd = dbConn.CreateCommand())
            {
                if (OutParams.Length > 0)
                    OutValues.Add("Parameters", OutParams.ToString());
                List<String> ParamNames = new List<string>(OutValues.Keys);
                ParamNames.AddRange(OutIndexes.Keys);

                StringBuilder OutSql = new StringBuilder("UPDATE " + TableName(null));
                bool FirstVariable = true;
                foreach (String Param in OutValues.Keys)
                {
                    OutSql.Append((FirstVariable ? " SET " : ", ") + "\"" + Param.ToUpper() + "\"=:" + Param);
                    FirstVariable = false;
                }



                FirstVariable = true;
                if (OutIndexes.Count > 0)
                    foreach (String Param in OutIndexes.Keys)
                    {
                        OutSql.Append((FirstVariable ? " WHERE " : " AND ") + "\"" + Param.ToUpper() + "\"=:" + Param);
                        FirstVariable = false;
                    }

                dCmd.CommandText = OutSql.ToString();
                dCmd.Prepare();

                foreach (KeyValuePair<String, Object> kvp in OutValues)
                {
                    DbParameter DbParam = dCmd.CreateParameter();
                    DbParam.ParameterName = kvp.Key;
                    DbParam.Value = kvp.Value;
                    dCmd.Parameters.Add(DbParam);
                }                 
                foreach (KeyValuePair<String, Object> kvp in OutIndexes)
                {
                    DbParameter DbParam = dCmd.CreateParameter();
                        DbParam.ParameterName = kvp.Key;
                        DbParam.Value = kvp.Value;
                        dCmd.Parameters.Add(DbParam);
                    }

                using (DbDataReader dRd = dCmd.ExecuteReader())
                { }
                //dCmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Write out our element as a serializable component        
        /// </summary>
        /// <param name="xW"></param>
        public void WriteXml(XmlTextWriter xW)
        {
            WriteXml(xW, "");
        }

        /// <summary>
        /// Write out our element as a serializable component
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="Prefix"></param>
        public void WriteXml(XmlTextWriter xW, String Prefix)
        {
            Object inVal;
            if (String.IsNullOrEmpty(Prefix))
                xW.WriteStartElement(this.GetType().Name);
            foreach (PropertyInfo pI in this.GetType().GetProperties())
                if (pI.CanRead && pI.CanWrite && (inVal = pI.GetValue(this, null)) != null)
                    if (inVal is MM_Serializable)
                        (inVal as MM_Serializable).WriteXml(xW, Prefix + pI.Name + ".");
                    else
                        xW.WriteAttributeString(Prefix + pI.Name, GetStringForXml(inVal));
            foreach (FieldInfo fI in this.GetType().GetFields())
                if ((inVal = fI.GetValue(this)) != null)
                    if (inVal is MM_Serializable)
                        (inVal as MM_Serializable).WriteXml(xW, Prefix + fI.Name + ".");
                    else
                        xW.WriteAttributeString(Prefix + fI.Name, GetStringForXml(inVal));
            if (String.IsNullOrEmpty(Prefix))
                xW.WriteEndElement();
        }

        /// <summary>
        /// Write out our element as a serializable component        
        /// </summary>
        /// <param name="xW"></param>
        public void WriteXmlForSerialization(XmlTextWriter xW)
        {
            WriteXmlForSerialization(xW, "");
        }

        /// <summary>
        /// Write out our element as a serializable component
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="Prefix"></param>
        public void WriteXmlForSerialization(XmlTextWriter xW, String Prefix)
        {
            Object inVal;
            if (String.IsNullOrEmpty(Prefix))
                xW.WriteStartElement(this.GetType().Name);
            foreach (PropertyInfo pI in this.GetType().GetProperties())
                if (pI.CanRead && pI.CanWrite && (inVal = pI.GetValue(this, null)) != null)
                    if (inVal is MM_Serializable)
                        (inVal as MM_Serializable).WriteXmlForSerialization(xW, Prefix + pI.Name + ".");
                    else if (MM_Serializable_Target.GetTargetType(pI) == MM_Serializable_Target.enumTargetType.FieldValue)
                        xW.WriteAttributeString(Prefix + pI.Name, GetStringForXml(inVal));
            foreach (FieldInfo fI in this.GetType().GetFields())
                if ((inVal = fI.GetValue(this)) != null)
                    if (inVal is MM_Serializable)
                        (inVal as MM_Serializable).WriteXmlForSerialization(xW, Prefix + fI.Name + ".");
                    else if (MM_Serializable_Target.GetTargetType(fI) == MM_Serializable_Target.enumTargetType.FieldValue)
                        xW.WriteAttributeString(Prefix + fI.Name, GetStringForXml(inVal));
            if (String.IsNullOrEmpty(Prefix))
                xW.WriteEndElement();
        }
        /// <summary>
        /// Return our target database table name
        /// </summary>
        /// <param name="Repository"></param>
        /// <returns></returns>
        private String TableName(MM_Repository Repository)
        {
            if (this is MM_Database_Model)
                return "MM_DATABASE_MODEL";
            else if (Repository != null && Repository.EditMode && (this is MM_Substation || this is MM_Line))
                return this.GetType().Name.ToUpper() + "_EDITOR";
            else
                return this.GetType().Name.ToUpper();
        }

        /// <summary>
        /// Write data from within our parameter to the outgoing RDB table
        /// </summary>
        /// <param name="Repository"></param>
        public void WriteRdb(MM_Repository Repository)
        {
            Dictionary<String, Object> OutValues = new Dictionary<string, Object>();
            StringBuilder OutParams = new StringBuilder();
            foreach (MemberInfo mI in this.GetType().GetMembers(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic))
                if (mI is PropertyInfo || mI is FieldInfo)
                {
                    MM_Serializable_Target.enumTargetType Target = MM_Serializable_Target.GetTargetType(mI);
                    if (Target == MM_Serializable_Target.enumTargetType.FieldValue)
                        OutValues.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.FieldUnique)
                        OutValues.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterValue)
                        OutParams.AppendLine(mI.Name + "=" + GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterXml)
                        OutParams.AppendLine((string)GetValueForOutput(mI));
                }

            using (DbCommand dCmd = Repository.Db.Database.CreateCommand())
            {
                List<String> ParamNames = new List<string>(OutValues.Keys);
                if (OutParams.Length > 0)
                    ParamNames.Add("Parameters");

                StringBuilder OutSql = new StringBuilder("INSERT INTO " + TableName(Repository) + " (" + String.Join(", ", ParamNames.ToArray()) + ") VALUES ");
                for (int a = 0; a < ParamNames.Count; a++)
#if DotNetOracle
                    if (ParamNames[a] == "ID" && Repository.Db.Database is System.Data.OracleClient.OracleConnection)
#else
                    if (ParamNames[a] == "ID" && Repository.Db.Database is Oracle.DataAccess.Client.OracleConnection)
#endif
                        if (this is MM_Database_Model)
                            OutSql.Append((a == 0 ? "(" : ",") + "SEQ_MM_DATABASE_MODEL.NEXTVAL");
                        else
                            OutSql.Append((a == 0 ? "(" : ",") + "SEQ_" + this.GetType().Name + ".NEXTVAL");
                    else
                        OutSql.Append((a == 0 ? "(" : ",") + ":" + ParamNames[a]);
                dCmd.CommandText = OutSql.ToString() + ")";
                dCmd.Prepare();
                foreach (KeyValuePair<String, Object> kvp in OutValues)
#if DotNetOracle
                    if (kvp.Key == "ID" && Repository.Db.Database is System.Data.OracleClient.OracleConnection)
#else
                    if (kvp.Key == "ID" && Repository.Db.Database is Oracle.DataAccess.Client.OracleConnection)
#endif
                    { }
                    else
                        dCmd.Parameters.Add(Repository.Db.CreateParameter(kvp.Key, kvp.Value));                    
                  

                if (OutParams.Length > 0)
                    dCmd.Parameters.Add(Repository.Db.CreateParameter("Parameters", OutParams.ToString()));     
                

                //If we have a new record, we'll need to pull in its ID.
#if DotNetOracle
                if (OutValues.ContainsKey("ID") && Repository.Db.Database is System.Data.OracleClient.OracleConnection)
#else
                if (OutValues.ContainsKey("ID") && Repository.Db.Database is Oracle.DataAccess.Client.OracleConnection)
#endif
                {
                    dCmd.CommandText += " RETURNING ID INTO :ID";
                    DbParameter dParam = dCmd.CreateParameter();
                    dParam.ParameterName = "ID";
                    dParam.DbType = DbType.Int32;
                    dParam.Direction = ParameterDirection.ReturnValue;
                    dCmd.Parameters.Add(dParam);
                    dCmd.ExecuteNonQuery();
                    this.ID = Convert.ToInt32(dParam.Value);
                }
                else
                    dCmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Write data from within our parameter to the outgoing RDB table
        /// </summary>
        /// <param name="DbConn"></param>
        public void WriteRdb(DbConnection DbConn)
        {
            Dictionary<String, Object> OutValues = new Dictionary<string, Object>();
            StringBuilder OutParams = new StringBuilder();
            foreach (MemberInfo mI in this.GetType().GetMembers(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic))
                if (mI is PropertyInfo || mI is FieldInfo)
                {
                    MM_Serializable_Target.enumTargetType Target = MM_Serializable_Target.GetTargetType(mI);
                    if (Target == MM_Serializable_Target.enumTargetType.FieldValue)
                        OutValues.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.FieldUnique)
                        OutValues.Add(mI.Name, GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterValue)
                        OutParams.AppendLine(mI.Name + "=" + GetValueForOutput(mI));
                    else if (Target == MM_Serializable_Target.enumTargetType.ParameterXml)
                        OutParams.AppendLine((string)GetValueForOutput(mI));
                }

            using (DbCommand dCmd = DbConn.CreateCommand())
            {
                List<String> ParamNames = new List<string>(OutValues.Keys);
                if (OutParams.Length > 0)
                    ParamNames.Add("Parameters");

                StringBuilder OutSql = new StringBuilder("INSERT INTO " + TableName(null) + " (" + String.Join(", ", ParamNames.ToArray()) + ") VALUES ");
                for (int a = 0; a < ParamNames.Count; a++)
#if DotNetOracle
                    if (ParamNames[a] == "ID" && DbConn is System.Data.OracleClient.OracleConnection)
#else
                    if (ParamNames[a] == "ID" && DbConn is Oracle.DataAccess.Client.OracleConnection)
#endif
                        if (this is MM_Database_Model)
                            OutSql.Append((a == 0 ? "(" : ",") + "SEQ_MM_DATABASE_MODEL.NEXTVAL");
                        else
                            OutSql.Append((a == 0 ? "(" : ",") + "SEQ_" + this.GetType().Name + ".NEXTVAL");
                    else
                        OutSql.Append((a == 0 ? "(" : ",") + ":" + ParamNames[a]);
                dCmd.CommandText = OutSql.ToString() + ")";
                dCmd.Prepare();
                foreach (KeyValuePair<String, Object> kvp in OutValues)
#if DotNetOracle
                    if (kvp.Key == "ID" && DbConn is System.Data.OracleClient.OracleConnection)
#else
                    if (kvp.Key == "ID" && Repository.Db.Database is Oracle.DataAccess.Client.OracleConnection)
#endif
                    { }
                    else
                    {
                        DbParameter DbParam = dCmd.CreateParameter();
                        DbParam.ParameterName = kvp.Key;
                        DbParam.Value = kvp.Value;
                        dCmd.Parameters.Add(DbParam);
                    }


                if (OutParams.Length > 0)
                {
                    DbParameter DbParam = dCmd.CreateParameter();
                    DbParam.ParameterName = "Parameters";
                    DbParam.Value = OutParams.ToString();
                    dCmd.Parameters.Add(DbParam);
                }

                //If we have a new record, we'll need to pull in its ID.
#if DotNetOracle
                if (OutValues.ContainsKey("ID") && DbConn is System.Data.OracleClient.OracleConnection)
#else
                if (OutValues.ContainsKey("ID") && DbConn is Oracle.DataAccess.Client.OracleConnection)
#endif
                {
                    dCmd.CommandText += " RETURNING ID INTO :ID";
                    DbParameter dParam = dCmd.CreateParameter();
                    dParam.ParameterName = "ID";
                    dParam.DbType = DbType.Int32;
                    dParam.Direction = ParameterDirection.ReturnValue;
                    dCmd.Parameters.Add(dParam);
                    dCmd.ExecuteNonQuery();
                    this.ID = Convert.ToInt32(dParam.Value);
                }
                else
                    dCmd.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Retrieve a property or field value
        /// </summary>
        /// <param name="mI"></param>
        /// <returns></returns>
        private object GetValueForOutput(MemberInfo mI)
        {
            Object OutObject = null;
            if (mI is FieldInfo)
                OutObject = (mI as FieldInfo).GetValue(this);
            else if (mI is PropertyInfo)
                OutObject = (mI as PropertyInfo).GetValue(this, null);
            else if (mI is MethodInfo)
                OutObject = (mI as MethodInfo).Invoke(this, null);
            else
                throw new InvalidOperationException("Values can only be retrieved for fields and properties!");

            if (OutObject == null)
                return DBNull.Value;
            else if (OutObject is XmlElement || OutObject is XmlDocument)
                using (StringWriter sW = new StringWriter())
                using (XmlTextWriter xW = new XmlTextWriter(sW))
                {
                    xW.Formatting = Formatting.Indented;
                    if (OutObject is XmlElement)
                        (OutObject as XmlElement).WriteTo(xW);
                    else
                        (OutObject as XmlDocument).WriteTo(xW);
                    return sW.ToString();
                }
            else if (OutObject is bool)
                return ((bool)OutObject ? 1 : 0);
            else if (OutObject is int[])
            {
                List<byte> OutBytes = new List<byte>();
                foreach (int i in (int[])OutObject)
                    OutBytes.AddRange(BitConverter.GetBytes(i));
                return OutBytes.ToArray();
            }

            else if (OutObject is string[])
                return String.Join(",", (string[])OutObject);

            else if (OutObject is TimeSpan)
                return ((TimeSpan)OutObject).TotalSeconds;
            else if (OutObject is Type)
                return ((Type)OutObject).Name;
            else if (OutObject is Image)
                using (MemoryStream OutStream = new MemoryStream())
                {
                    (OutObject as Image).Save(OutStream, System.Drawing.Imaging.ImageFormat.Bmp);
                    return OutStream.ToArray();
                }
          
            else if (OutObject is MM_Database_Model[])
            {
                StringBuilder sB = new StringBuilder();
                foreach (MM_Database_Model Model in (OutObject as MM_Database_Model[]))
                    if (Model != null)
                        sB.Append("," + Model.ID.ToString());
                return sB.ToString() + ",";
            }
            else if (OutObject is CIM_RdfID)
                return OutObject.ToString();
            else if (OutObject is Integration.MM_Boundary[])
                using (MemoryStream mS = new MemoryStream())
                {
                    foreach (Integration.MM_Boundary Bound in (OutObject as Integration.MM_Boundary[]))
                        if (Bound != null)
                            mS.Write(BitConverter.GetBytes(Bound.ID), 0, 4);
                    return mS.ToArray();
                }
            else if (OutObject is PointF)
                return ((PointF)OutObject).X.ToString() + "," + ((PointF)OutObject).Y.ToString();
            else if (OutObject is PointD)
                return ((PointD)OutObject).X.ToString() + "," + ((PointD)OutObject).Y.ToString();
            else if (OutObject is PointF[])
            {
                StringBuilder sB = new StringBuilder();
                foreach (PointF pt in (PointF[])OutObject)
                    sB.Append((sB.Length == 0 ? "" : ",") + pt.X.ToString() + "," + pt.Y.ToString());
                return sB.ToString();
            }
            else if (OutObject is UInt64[])
            {
                StringBuilder sB = new StringBuilder();
                foreach (UInt64 pt in (UInt64[])OutObject)
                    sB.Append((sB.Length == 0 ? "" : ",") + pt.ToString() );
                return sB.ToString();
            }
            else if (OutObject is PointD[])
                using (MemoryStream mS = new MemoryStream())
                {
                    foreach (PointD pt in ((PointD[])OutObject))
                    {
                        mS.Write(BitConverter.GetBytes(pt.X), 0, 8);
                        mS.Write(BitConverter.GetBytes(pt.Y), 0, 8);
                    }
                    return mS.ToArray();
                }
            else if (OutObject is MM_Serializable)
                return (OutObject as MM_Serializable).ID;
            else if (OutObject is float)
                return new Decimal((float)OutObject);
            else if (OutObject is double && !double.IsNaN((double)OutObject))
                return new Decimal((double)OutObject);
            
            else
                return OutObject;
        }
        #endregion
    }


    #region Type-specific functionality
    /// <summary>
    /// This class holds type-specific conversions for a serialiazble function
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public static class MM_Serializable<T>
    {
        /// <summary>
        /// Retrieve an object in a type-specific means
        /// </summary>
        /// <param name="TargetValue"></param>
        /// <param name="xDoc"></param>
        /// <param name="Repository">The data repository</param>
        /// <returns></returns>
        public static T ConvertObject(Object TargetValue, XmlDocument xDoc, MM_Repository Repository)
        {
            return (T)MM_Serializable.ConvertValue(typeof(T), TargetValue, xDoc, Repository);
        }

        /// <summary>
        /// Retrieve an object in a type-specific means
        /// </summary>
        /// <param name="TargetValue"></param>        
        /// <param name="Repository">The data repository</param>
        /// <returns></returns>
        public static T ConvertObject(Object TargetValue, MM_Repository Repository)
        {
            return (T)MM_Serializable.ConvertValue(typeof(T), TargetValue, null, Repository);
        }
    }
    #endregion
}